package owg.util.euclidian;

import owg.util.Calc;

/**
 * This is a rigid 2D vector class for abstracting the representation and computing of a wide range of values. 
 * Many of the methods are designed so to not allocate any heap space;
 * Excercise caution so you do not accidentally overwrite your vectors. 
 * In addition, many of the methods return the vector on which the call was made, so the calls can be chained.
 */
public class V2F implements VXF
	{
	public static final char[] COMPONENT_NAMES = {'x','y'};
	//Some standard vectors. Be very careful not to alter these. 
	//If assertions are enabled, they will be checked periodically for correctness.
	public static final V2F ZERO = new V2F();
	
	public static final V2F X = new V2F(1,0);
	public static final V2F Y = new V2F(0,1);
	
	public static final V2F NEG_X = new V2F(-1,0);
	public static final V2F NEG_Y = new V2F(0,-1);
	
	public static boolean testStandardVectors()
		{
		if(!ZERO.isZero())
			throw new RuntimeException("Fail");
		V2F tmp = new V2F();
		if(!X.equals(tmp.set(1,0)))
			throw new RuntimeException("Fail");
		if(!Y.equals(tmp.set(0,1)))
			throw new RuntimeException("Fail");
		if(!NEG_X.equals(tmp.set(-1,0)))
			throw new RuntimeException("Fail");
		if(!NEG_Y.equals(tmp.set(0,-1)))
			throw new RuntimeException("Fail");
		
		return true;
		}
	
	public float[] p = new float[2];
	/**
	 * Construct with zero length
	 */
	public V2F()
		{
		this(0,0);
		}
	/**
	 * Construct with the specified components
	 */
	public V2F(float x,float y)
		{
		p[0]=x;
		p[1]=y;
		}
	/**
	 * Construct from the float array, starting from the offset 
	 */
	public V2F(float[] vector, int offset)
		{
		this((float)vector[offset],(float)vector[offset+1]);
		}
	/**
	 * Construct from the float array, starting from the offset 
	 */
	public V2F(double[] vector, int offset)
		{
		this((float)vector[offset],(float)vector[offset+1]);
		}

	/**
	 * Construct from the byte array, starting from the offset 
	 */
	public V2F(byte[] vector, int offset)
		{
		this((float)vector[offset],(float)vector[offset+1]);
		}
	/**
	 * Set all components
	 * @return this vector
	 */
	public V2F set(float x,float y)
		{
		p[0]=x;
		p[1]=y;
		return this;
		}
	/**
	 * Set all components
	 * @return this vector
	 */
	public V2F set(V2F source)
		{
		p[0]=source.p[0];
		p[1]=source.p[1];
		return this;
		}
	/**
	 * Set all components
	 * @return this vector
	 */
	public V2F set(float[] src)
		{
		p[0]=(float)src[0];
		p[1]=(float)src[1];
		return this;
		}
	/**
	 * Set all components
	 * @return this vector
	 */
	public V2F set(double [] src)
		{
		p[0]=(float)src[0];
		p[1]=(float)src[1];
		return this;
		}

	/**
	 * Calculate the length
	 */
	public float len()
		{
		return (float)Math.sqrt(Math.pow(p[0],2)+Math.pow(p[1],2));
		}
	/**Calculate the length of this-other*/
	public float distance(V2F other)
		{
		return (float)Math.sqrt(Math.pow(p[0]-other.p[0],2)+Math.pow(p[1]-other.p[1],2));
		}
	/**Calculate the squared length of this-other*/
	public float sqDistance(V2F other)
		{
		return (float)(Math.pow(p[0]-other.p[0],2)+Math.pow(p[1]-other.p[1],2));
		}
	/**Calculate the angle(radians) between this and other*/
	public float angle(V2F other)
		{
		return (float)Calc.acos(dot(other)/(len()*other.len()));
		}
	
	/**
	 * Make the length equal to 1 while keeping the direction.
	 * Makes the vector (1,0) if length is 0.
	 * @return this vector
	 */
	public V2F normalize()
		{
		float d = (float)(Math.sqrt(Math.pow(p[0],2)+Math.pow(p[1],2)));
		if (d==0)
			{
			p[0]=1;
			p[1]=0;
			}
		else
			{
			p[0]/=d;
			p[1]/=d;
			}
		return this;
		}
	/**
	 * Multiply all components with the value
	 * @return this vector
	 */
	public V2F multiply(float m)
		{
		p[0]*=m;
		p[1]*=m;
		return this;
		}
	/**
	 * Multiply all components with the respective values
	 * @return this vector
	 */
	public V2F multiply(float mx, float my)
		{
		p[0]*=mx;
		p[1]*=my;
		return this;
		}
	/**
	 * Add the components to the current values
	 * @return this vector
	 */
	public V2F add(float x,float y)
		{
		p[0]+=x;
		p[1]+=y;
		return this;
		}
	/**
	 * Add the vector's components to the current values. 
	 * This method allows the input to be null.
	 * @return this vector
	 */
	public V2F addAllowNull(V2F v)
		{
		if(v!=null)
			{
			p[0]+=v.p[0];
			p[1]+=v.p[1];
			}
		return this;
		}

	/**
	 * Add the vector's components to the current values
	 * @return this vector
	 */
	public V2F add(V2F v)
		{
		p[0]+=v.p[0];
		p[1]+=v.p[1];
		return this;
		}
	/**
	 * Subtract the vector's components from the current values
	 * @return this vector
	 */
	public V2F sub(V2F v)
		{
		p[0]-=v.p[0];
		p[1]-=v.p[1];
		return this;
		}
	/**
	 * Add the vector multiplied with the value
	 * @return this vector
	 */
	public V2F add(V2F v,float multiplicator)
		{
		p[0]+=multiplicator*v.p[0];
		p[1]+=multiplicator*v.p[1];
		return this;
		}
	/**
	 * Rotate this vector by 90 degrees, counter-clockwise.
	 * @return this vector
	 */
	public V2F perpendicular()
		{
		float tmp = p[0]; 
		p[0] = p[1];
		p[1] = -tmp;
		return this;
		}
		
	/**
	 * Rotate this vector by the indicated radians, counter-clockwise.
	 * @return this vector
	 */
	public V2F rotate(float angle)
		{
		float h,v;
		h = (float)Math.cos(angle);
		v = -(float)Math.sin(angle);
		float tmp = p[0]; 
		p[0] = h*p[0]-v*p[1];
		p[1] = v*tmp+h*p[1];
		return this;
		}
	/**
	 * Make this vector the average of this and the specified vector(weight=0 -> this, weight=1 -> other)
	 * @return this vector
	 */
	public V2F makeAverage(V2F other,float weight)
		{
		p[0]=(p[0]*(1-weight)+other.p[0]*weight);
		p[1]=(p[1]*(1-weight)+other.p[1]*weight);
		return this;
		}

	/**
	 * Make this vector the average of this and the specified components(weight=0 -> this, weight=1 -> components)
	 * @return this vector
	 */
	public V2F makeAverage(float x, float y, float weight)
		{
		p[0]=(p[0]*(1-weight)+x*weight);
		p[1]=(p[1]*(1-weight)+y*weight);
		return this;
		}
	/**
	 * Return the dot product of this and the specified vector
	 */
	public float dot(V2F v)
		{
		return p[0]*v.p[0]+p[1]*v.p[1];
		}
	/**
	 * Return the dot product of this and the specified vector
	 */
	public float dot(float x,float y)
		{
		return p[0]*x+p[1]*y;
		}
	/**
	 * Return the x-component
	 */
	public float x()
		{
		return p[0];
		}
	/**
	 * Return the y-component
	 */
	public float y()
		{
		return p[1];
		}
	/**
	 * Return the z-component(always zero)
	 */
	public float z()
		{
	    	return 0;
		}
	/**
	 * Return the backing array
	 */
	public float[] array()
	{
		return p;
	}
	/**
	 * Set the x-component
	 * @return this vector
	 */
	public V2F x(float x)
		{
		p[0]=x;
		return this;
		}
	/**
	 * Set the y-component
	 * @return this vector
	 */
	public V2F y(float y)
		{
		p[1]=y;
		return this;
		}
	/**
	 * Return a new float array representing this vector
	 * Note that the p member of this class can be used directly if the type matches and a copy is not needed.
	 */
	public float[] toFloat()
		{
		float[] result = {(float)p[0],(float)p[1]};
		return result;
		}
	/**Insert 2 elements into the given array starting from the indicated index,
	 * representing the x, y value of this vector.*/
	public void toFloat(float[] vertices, int i)
		{
		vertices[i+0] = (float)p[0];
		vertices[i+1] = (float)p[1];
		}
	/**
	 * Return a new double array representing this vector.
	 * Note that the p member of this class can be used directly if the type matches and a copy is not needed.
	 */
	public double[] toDouble()
		{
		double[] result = {(double)p[0],(double)p[1]};
		return result;
		}
	/**Insert 2 elements into the given array starting from the indicated index,
	 * representing the x, y value of this vector.*/
	public void toDouble(double[] vertices, int i)
		{
		vertices[i+0] = (double)p[0];
		vertices[i+1] = (double)p[1];
		}
	/**
	 * Return a signed byte array representing this vector
	 */
	public byte[] toByte()
		{
		byte[] result = {(byte)p[0],(byte)p[1]};
		return result;
		}
	/**
	 * Return a signed short array representing this vector
	 */
	public short[] toShort()
		{
		short[] result = {(short)p[0],(short)p[1]};
		return result;
		}
	/**
	 * Return a signed int array representing this vector
	 */
	public int[] toInt()
		{
		int[] result = {(int)p[0],(int)p[1]};
		return result;
		}
	/**
	 * Return a signed long array representing this vector
	 */
	public long[] toLong()
		{
		long[] result = {(long)p[0],(long)p[1]};
		return result;
		}
	/**
	 * Make a copy
	 */
	@Override
	public V2F clone()
		{
		return new V2F(p[0],p[1]);
		}
	/**Return a new V3D with the values from this vector. The z value is set to 0.*/
	public V3D asV3D()
		{
		return new V3D((double)p[0], (double)p[1], 0);
		}
		
	/**Return a new V3F with the values from this vector. The z value is set to 0.*/
	public V3F asV3F()
		{
		return new V3F((float)p[0], (float)p[1], 0);
		}
	/**Return a new V2D with the values from this vector*/
	public V2D asV2D()
		{
		return new V2D((double)p[0], (double)p[1]);
		}
		
	/**Return a new V2F with the values from this vector*/
	public V2F asV2F()
		{
		return new V2F((float)p[0], (float)p[1]);
		}
	/**
	 * Print the coordinates. 
	 */
	@Override
	public  String toString()
		{
		return "x:"+p[0]+", y:"+p[1]+" ";
		}
		
	/**
	 * Parse a string into a V2F. The String should correspond to the output from {@link V2F#toString()}
	 * @param str A string in the form<br/> 
	 * x:[x],y:[y]<br/>
	 * where [x] is the numerical x coordinate value and soforth. Alternatively, the identifiers (x:) may be omitted.
	 * The values must appear in order in any case.
	 * @return A V2F object with the values defined in the string.
	 */
	public static V2F parseV2F(String str) throws IllegalArgumentException
		{
		return new V2F(str);
		}
	/**
	 * Parse a string into a V2F. The String should correspond to the output from {@link V2F#toString()}
	 * @param str A string in the form<br/> 
	 * x:[x],y:[y]<br/>
	 * where [x] is the numerical x coordinate value and soforth. Alternatively, the identifiers (x:) may be omitted.
	 * The values must appear in order in any case.
	 */
	public V2F(String str) throws IllegalArgumentException
		{
		String[] in = str.split(",");
		if(in.length < 2)
			throw new IllegalArgumentException("Vector must have 2 components. Found: "+in.length+" in "+str);
		try
			{
			for(int i = 0; i<2; i++)
				{
				int pos = in[i].lastIndexOf(':');
				if(pos == -1)
					p[i] = Float.parseFloat(in[i]);
				else
					p[i] = Float.parseFloat(in[i].substring(pos+1));
				}
			}
		catch (NumberFormatException e)
			{
			throw new IllegalArgumentException("Error in parsing "+str, e);
			}
		}
	/**
	 * Accelerate in the current direction by an absolute value. 
	 * If length is under the negative of the value, the vector is set to zero.
	 * @return this vector*/
	public V2F accelerate(float d)
		{
		float sp = len();
		if(sp!=0)
			{
			if (sp<=-d)
				{
				p[0]=0; 
				p[1]=0;
				return this;
				}
			d=1+d/sp;
			p[0]*=d; 
			p[1]*=d;
			}
		return this;
		}
	/**
	 * Accelerate towards becoming identical to other by an absolute value.
	 * @param other the vector to move towards
	 * @param the amount to increment this vector by, in the direction of other. Non-positive values have no effect.
	 * @return this vector*/
	public V2F accelerateTowards(V2F other, float d)
		{
		if(d <= 0)
			return this;
		float sp = distance(other);
		if (sp<=d)
			{
			p[0]=other.p[0]; 
			p[1]=other.p[1];
			return this;
			}
		d=d/sp;
		p[0]=p[0]*(1-d)+other.p[0]*(d); 
		p[1]=p[1]*(1-d)+other.p[1]*(d);
		return this;
		}
	/**
	 * Calculate the squared length. Cheaper than calculating length since there is no square root.
	 */
	public float sqLen()
		{
		return (float)(Math.pow(p[0],2)+Math.pow(p[1],2));
		}
	/**
	 * Returns whether this is bitwise equal to the other vector. If other is not a V2F then this will always return false.
	 */
	@Override
	public boolean equals(Object other)
		{
		if(other instanceof V2F)
			return (((V2F)other).p[0]==p[0] && ((V2F)other).p[1]==p[1]);
		return false;
		}
	@Override
	public int hashCode()
		{
		return (int)(p[1]*256+p[0]*65536);
		}
	/**Checks if this vector is a multiple of the other vector.
	 * Returns false if exactly one vector is zero.
	 * Returns true if both vectors are zero.*/
	public boolean isMultipleOf(V2F other)
		{
		if(isZero() ^ other.isZero())
			return false;
		if(isZero() && other.isZero())
			return true;
		if(p[0] == 0) //This is on y-line
			return other.p[0] == 0; //True only if other is on y-line
		float m = other.p[0]/p[0];
		return (Math.abs(other.p[1]-p[1]*m) < Float.MIN_VALUE*10);
		}
	/**Checks if all components are exactly zero.*/
	public boolean isZero()
		{
		return p[0]==0 && p[1]==0;
		}
	/**Returns the manhattan length.*/
	public float manLen()
		{
		return Math.abs(p[0])+Math.abs(p[1]);
		}
	/**
	 * Optimal reversal of vector. Functionally equivalent to multiply(-1)
	 * @return This vector
	 */
	public V2F reverse()
		{
		p[0] = -p[0];
		p[1] = -p[1];
		return this;
		}
	/**Generate a random vector where each component ranges from -1 to 1.*/
	public static V2F manhattanRandom()
		{
		return new V2F(1-2*(float)Math.random(),1-2*(float)Math.random());
		}
	/**Generate a random (approximately) unit vector using the fastest math available.
	 * The distribution is uniform across the surface of a sphere.*/
	public static V2F fastRandom()
		{
		float a = Calc.PI*2*Calc.random();
		return new V2F(Calc.fastCos(a), -Calc.fastSin(a));		
		}
	/**Generate a random unit vector.
	 * The distribution is uniform across the contour of a circle.*/
	public static V2F unitRandom()
		{
		float a = Calc.PI*2*(float)Math.random();
		return new V2F((float)Math.cos(a), (float)Math.sin(a));		
		}
	}
